package com.symaster.common.image.util;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ArrayUtil;
import com.symaster.common.ExceptionFactory;
import com.symaster.common.geom.entity.Polygon;
import com.symaster.common.geom.entity.Vector;
import com.symaster.common.geom.util.PointUtil;
import com.symaster.common.util.ArrayUtils;
import com.symaster.common.util.IOUtil;
import org.apache.commons.imaging.ImageInfo;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.Imaging;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.opencv.core.Point;
import org.opencv.core.*;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import sun.font.FontDesignMetrics;
import sun.misc.BASE64Encoder;

import javax.imageio.*;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.*;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

import static org.opencv.imgcodecs.Imgcodecs.imencode;

/**
 * @author yinmiao
 * @date 2021-09-14 15:38
 */
public class ImageUtil {

    public static final String DENSITY_UNITS_NO_UNITS = "00";
    public static final String DENSITY_UNITS_PIXELS_PER_INCH = "01";
    public static final String DENSITY_UNITS_PIXELS_PER_CM = "02";
    public static final Map<String, String> imageTypeMap = new HashMap<>();
    public static final String TYPE_JPEG = "jpg";
    public static final String TYPE_TIFF = "tif";
    public static final String TYPE_BMP = "bmp";
    public static final String TYPE_PNG = "png";

    static {
        setupCvCore();
        imageTypeMap.put(TYPE_JPEG, "jpg/jpeg");
        imageTypeMap.put(TYPE_PNG, "png");
        imageTypeMap.put(TYPE_BMP, "bmp");
        imageTypeMap.put(TYPE_TIFF, "tif/tiff");
    }

    public static void main(String[] args) throws Exception {
        FileInputStream fileInputStream = new FileInputStream("D:\\Documents\\Pictures\\MGZC-18.png");
        Mat mat = toMat(fileInputStream);

        scaleMat(mat, 600);
        // List<Polygon> polygonList = getPolygon(mat);
        // Polygon minRectangle = PointUtil.getMinRectangle(polygonList);
        // Mat mat1 = interceptPolygon(mat, minRectangle, true, new Scalar(0, 0, 0, 0));
        Imgcodecs.imwrite("D:\\out.png", mat);
    }

    public static void setupCvCore() {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    /**
     * 截取图片中多边形区域,
     *
     * @param rect 使用最小轮廓
     */
    public static Mat interceptPolygon(Mat mat, Polygon polygon, boolean rect, Scalar backgroundColor) {
        double[] xPoints = polygon.getXpoints();
        double[] yPoints = polygon.getYpoints();
        Point[] points = new Point[xPoints.length];
        for (int i = 0; i < xPoints.length; i++) points[i] = new Point(xPoints[i], yPoints[i]);
        ArrayList<MatOfPoint> maskArea = new ArrayList<>();
        MatOfPoint matOfPoint = new MatOfPoint();
        matOfPoint.fromArray(points);
        maskArea.add(matOfPoint);
        Mat mask = new Mat(new Size(mat.width(), mat.height()), mat.type(), new Scalar(0, 0, 0, 0));
        Mat bgMat = new Mat(new Size(mat.width(), mat.height()), mat.type(), backgroundColor);
        Imgproc.fillPoly(mask, maskArea, new Scalar(255, 255, 255, 255), 8, 0);
        mat.copyTo(bgMat, mask);

        Mat item;
        if (rect) {
            item = new Mat(bgMat, new Rect((int) polygon.getX(), (int) polygon.getY(), (int) polygon.getWidth(), (int) polygon.getHeight()));
            bgMat.release();
        } else {
            item = bgMat;
        }

        mask.release();
        return item;
    }

    /**
     * 获取图片DPI
     */
    public static int getDPI(byte[] byteArray) {
        ImageInfo imageInfo = null;
        try {
            imageInfo = Imaging.getImageInfo(byteArray);
        } catch (ImageReadException | IOException e) {
            e.printStackTrace();
        }
        if (imageInfo == null) {
            return 0;
        }
        // return imageInfo.getPhysicalHeightDpi();
        return imageInfo.getPhysicalWidthDpi();
    }

    public static String toBase64(Mat capImg) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            ImageIO.write(mat2BI(capImg), "jpg", baos);
        } catch (IOException e) {
            e.printStackTrace();
        }
        byte[] bytes = baos.toByteArray();
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encodeBuffer(Objects.requireNonNull(bytes));
    }

    private static BufferedImage mat2BI(Mat mat) {
        int dataSize = mat.cols() * mat.rows() * (int) mat.elemSize();
        byte[] data = new byte[dataSize];
        mat.get(0, 0, data);
        int type = mat.channels() == 1 ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_3BYTE_BGR;

        if (type == BufferedImage.TYPE_3BYTE_BGR) {
            for (int i = 0; i < dataSize; i += 3) {
                byte blue = data[i];
                data[i] = data[i + 2];
                data[i + 2] = blue;
            }
        }
        BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), type);
        image.getRaster().setDataElements(0, 0, mat.cols(), mat.rows(), data);

        return image;
    }

    public static Mat loadMat(String path) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(path);
            byte[] bytes = new byte[fileInputStream.available()];
            int read = fileInputStream.read(bytes);
            return toMat(bytes);
        } catch (IOException e) {
            e.printStackTrace();
            throw ExceptionFactory.getDefaultException(String.format("IO异常 %s", e.getMessage()));
        } finally {
            IOUtil.close(fileInputStream);
        }
    }

    public static Mat toMat(byte[] byteArray) {
        if (byteArray == null || byteArray.length == 0) {
            throw ExceptionFactory.getDefaultException("byteArray is empty");
        }

        Mat cache = null;
        try {
            cache = new Mat(1, byteArray.length, 0);
            cache.put(0, 0, byteArray);
            Mat mat = Imgcodecs.imdecode(cache, -1);
            if (mat.empty()) {
                throw ExceptionFactory.getDefaultException("Mat Is Empty");
            }
            return mat;
        } finally {
            IOUtil.close(cache);
        }
    }

    /**
     * 输入流转mat
     */
    public static Mat toMat(InputStream inputStream) {
        ByteArrayOutputStream os = null;
        byte[] bytes;
        try {
            os = new ByteArrayOutputStream();
            IOUtils.copy(inputStream, os);
            bytes = os.toByteArray();
        } catch (Throwable e) {
            e.printStackTrace();
            throw ExceptionFactory.getDefaultException(String.format("%s", e));
        } finally {
            IOUtil.close(inputStream, os);
        }

        return toMat(bytes);
    }

    /**
     * mat转输入流
     */
    public static InputStream toInputStream(Mat mat) {
        byte[] byteArray = toByteArray(mat, ".jpg");
        return new ByteArrayInputStream(byteArray);
    }

    /**
     * mat转字节数组
     *
     * @param format 文件格式
     */
    public static byte[] toByteArray(Mat mat, String format) {
        MatOfByte mob = new MatOfByte();
        imencode(format, mat, mob);
        return mob.toArray();
    }

    /**
     * opencv mat 转 bufferedImage
     * <p>
     * 仅支持三通道和四通道mat
     */
    public static Image toBufferedImage(Mat mat) {

        if (3 == mat.channels()) {
            return matBGR2ImageRGB(mat);
        }

        if (4 == mat.channels()) {
            return matBGRA2ImageRGBA(mat);
        }

        return null;
    }

    /**
     * opencv BGR 转 bufferedImage
     */
    public static Image matBGR2ImageRGB(Mat mat) {
        int len = 3 * mat.cols() * mat.rows();
        byte[] buff = new byte[len];
        mat.get(0, 0, buff);

        BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), BufferedImage.TYPE_3BYTE_BGR);
        final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        System.arraycopy(buff, 0, targetPixels, 0, buff.length);

        return image;
    }

    /**
     * opencv BGRA 转 bufferedImage
     */
    public static Image matBGRA2ImageRGBA(Mat mat) {
        int len = 4 * mat.cols() * mat.rows();
        byte[] buff = new byte[len];
        mat.get(0, 0, buff);

        BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), BufferedImage.TYPE_4BYTE_ABGR);
        final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        for (int i = 0; i < buff.length; i += 4) {
            targetPixels[i + 3] = buff[i + 2];
            targetPixels[i + 2] = buff[i + 1];
            targetPixels[i + 1] = buff[i];
            targetPixels[i] = buff[i + 3];
        }

        return image;
    }

    /**
     * bufferedImageRGBA 转 mat BGRA
     */
    public static Mat bufferedImageRGBA2MatBGRA(BufferedImage image) {
        int len = 4 * image.getWidth() * image.getHeight();
        byte[] targetBytes = new byte[len];
        final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();

        Mat mat = new Mat(new Size(image.getWidth(), image.getHeight()), CvType.CV_8UC4);
        for (int i = 0; i < pixels.length; i += 4) {
            targetBytes[i] = pixels[i + 1];
            targetBytes[i + 1] = pixels[i + 2];
            targetBytes[i + 2] = pixels[i + 3];
            targetBytes[i + 3] = pixels[i];
        }

        mat.put(0, 0, targetBytes);
        return mat;
    }

    /**
     * bufferedImageRGB 转 mat BGR
     */
    public static Mat bufferedImageRGB2MatBGR(BufferedImage image) {

        byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        Mat mat = new Mat(new Size(image.getWidth(), image.getHeight()), CvType.CV_8UC3);
        mat.put(0, 0, pixels);
        return mat;
    }

    /**
     * 等比例缩放图片致指定大小
     *
     * @param maxSize 指定大小
     */
    public static void scaleMat(Mat src, int maxSize) {
        int width = src.width();
        int height = src.height();

        double scale = PointUtil.getScale(Math.max(width, height), maxSize);
        Imgproc.resize(src, src, new Size(width * scale, height * scale), 1, 1, Imgproc.INTER_AREA);
    }

    /**
     * 等比例缩放图片致指定大小
     *
     * @param scale 缩放因子
     */
    public static void scaleMat(Mat src, double scale) {
        int width = src.width();
        int height = src.height();

        Imgproc.resize(src, src, new Size(width * scale, height * scale), 1, 1, Imgproc.INTER_AREA);
    }

    /**
     * 移动
     */
    public static void translateTransform(Mat imageMat, double xn, double yn) {

        MatOfPoint2f matOfPoint2f = null;
        MatOfPoint2f matOfPoint2f1 = null;
        Mat affineTransform = null;
        try {
            Size size = imageMat.size();
            Point[] points = new Point[3];
            points[0] = new Point(0, 0);
            points[1] = new Point(1, 0);
            points[2] = new Point(0, 1);
            matOfPoint2f = new MatOfPoint2f(points);

            Point[] points1 = new Point[3];
            points1[0] = new Point(xn, yn);
            points1[1] = new Point((1 + xn), yn);
            points1[2] = new Point(xn, (1 + yn));
            matOfPoint2f1 = new MatOfPoint2f(points1);

            affineTransform = Imgproc.getAffineTransform(matOfPoint2f, matOfPoint2f1);
            Imgproc.warpAffine(imageMat, imageMat, affineTransform, size);
        } finally {
            IOUtil.close(matOfPoint2f, matOfPoint2f1, affineTransform);
        }
    }

    /**
     * 顺时针旋转
     */
    public static Mat rotateTransform(Mat mat, double angele, double centerX, double centerY) {
        return reverseRotateTransform(mat, 360 - angele, centerX, centerY);
    }

    /**
     * 逆时针旋转
     */
    public static Mat reverseRotateTransform(Mat mat, double angele, double centerX, double centerY) {
        Mat result = new Mat();
        Mat affineTrans = Imgproc.getRotationMatrix2D(new Point(centerX, centerY), angele, 1.0);
        Imgproc.warpAffine(mat, result, affineTrans, mat.size(), Imgproc.INTER_NEAREST);
        return result;
    }

    /**
     * 创建掩码
     */
    public static Mat createMask(double width, double height, double[] xPoints, double[] yPoints) {
        List<Point> points = new ArrayList<>();
        for (int i = 0; i < xPoints.length; i++) {
            points.add(new Point(xPoints[i], yPoints[i]));
        }
        return createMask(width, height, points);
    }

    /**
     * 创建掩码
     */
    public static Mat createMask(double width, double height, Polygon polygon) {
        return createMask(width, height, polygon.getXpoints(), polygon.getYpoints());
    }

    public static Mat createMask(double width, double height, Polygon... polygon) {
        Mat mask = null;
        List<MatOfPoint> maskArea = null;
        try {
            maskArea = new ArrayList<>();
            for (Polygon polygon1 : polygon) {
                MatOfPoint maskPoints = new MatOfPoint();
                List<Point> collect = polygon1.toPointList().stream().map(e -> new Point(e.x, e.y)).collect(Collectors.toList());
                maskPoints.fromList(collect);
                maskArea.add(maskPoints);
            }
            mask = new Mat(new Size(width, height), CvType.CV_8U, new Scalar(0));
            Imgproc.fillPoly(mask, maskArea, new Scalar(255, 255, 255), 8, 0);
            return mask;
        } finally {
            IOUtil.close(maskArea);
        }
    }

    /**
     * 创建掩码
     */
    public static Mat createMask(double width, double height, List<Point> points) {

        Mat mask = null;
        MatOfPoint maskPoints = null;
        List<MatOfPoint> maskArea = null;
        try {
            maskArea = new ArrayList<>();
            maskPoints = new MatOfPoint();
            maskPoints.fromList(points);
            maskArea.add(maskPoints);
            mask = new Mat(new Size(width, height), CvType.CV_8U, new Scalar(0));
            Imgproc.fillPoly(mask, maskArea, new Scalar(255, 255, 255), 8, 0);
            return mask;
        } finally {
            IOUtil.close(maskArea);
            IOUtil.close(maskPoints);
        }
    }

    public static Mat createMaskFillColor(double width, double height, Polygon polygon, Scalar scalar) {
        return createMaskByPolygons(width, height, Collections.singletonList(polygon), new Scalar(255));
    }

    public static Mat createMaskByPolygons(double width, double height, List<Polygon> polygons) {
        return createMaskByPolygons(width, height, polygons, new Scalar(255));
    }

    public static Mat createMaskByPolygons(double width, double height, List<Polygon> polygons, Scalar fillColor) {
        List<MatOfPoint> collect = null;
        try {
            Mat mask = new Mat(new Size(width, height), CvType.CV_8U, new Scalar(0));
            collect = polygons.stream().map(PointUtil::polygon2MatOfPoint).collect(Collectors.toList());
            Imgproc.fillPoly(mask, collect, fillColor, 8, 0);
            return mask;
        } finally {
            IOUtil.close(collect);
        }
    }

    /**
     * 轮廓查找
     */
    public static List<Polygon> getPolygon(Mat src) {
        return getPolygon(src, new Scalar(0, 0, 0), new Scalar(50, 50, 50));
    }

    /**
     * 轮廓查找
     */
    public static List<Polygon> getPolygon(Mat src, Scalar low, Scalar upper) {
        return getPolygon(src, low, upper, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
    }

    /**
     * 轮廓查找
     */
    public static List<Polygon> getPolygon(Mat src, Scalar low, Scalar upper, int model, int method) {

        Mat cacheMat = null;
        Mat hierarchy = null;
        List<MatOfPoint> list = null;
        try {
            cacheMat = new Mat();
            list = new ArrayList<>();
            hierarchy = new Mat();

            Imgproc.cvtColor(src, cacheMat, Imgproc.COLOR_BGR2GRAY);
            Core.inRange(cacheMat, low, upper, cacheMat);
            Imgproc.findContours(cacheMat, list, hierarchy, model, method);
            return list.stream().map(PointUtil::matOfPoint2Polygon)
                    .filter(e -> (e.getHeight() > 5 && e.getWidth() > 5))
                    .collect(Collectors.toList());
        } finally {
            IOUtil.close(cacheMat, hierarchy);
            IOUtil.close(list);
        }
    }

    /**
     * 轮廓查找
     */
    public static List<Polygon> getPolygon(Mat src, Scalar low, Scalar upper, int model, int method, Point offset) {

        Mat cacheMat = null;
        Mat hierarchy = null;
        List<MatOfPoint> list = null;
        try {
            cacheMat = new Mat();
            list = new ArrayList<>();
            hierarchy = new Mat();

            Core.inRange(src, low, upper, cacheMat);
            Imgproc.findContours(cacheMat, list, hierarchy, model, method, offset);
            return list.stream().map(PointUtil::matOfPoint2Polygon)
                    .filter(e -> (e.getHeight() > 5 && e.getWidth() > 5))
                    .collect(Collectors.toList());
        } finally {
            IOUtil.close(cacheMat, hierarchy);
            IOUtil.close(list);
        }
    }

    /**
     * 截取多边形区域
     *
     * @param src             原图片
     * @param polygonList     多边形列表
     * @param rect            是否剪切最小矩形 默认true
     * @param backgroundColor 多余部分背景颜色
     */
    public static List<Mat> splitByPolygon(Mat src, Iterable<Polygon> polygonList, Boolean rect, Scalar backgroundColor) {

        // 参数校验
        if (src == null) {
            throw new IllegalArgumentException("src == null");
        }
        if (polygonList == null) {
            throw new IllegalArgumentException("polygonList == null");
        }
        if (rect == null) {
            rect = true;
        }
        if (backgroundColor == null) {
            backgroundColor = new Scalar(255, 255, 255, 255);
        }

        List<Mat> result = new ArrayList<>();
        Mat cacheMat = src.clone();

        for (Polygon polygon : polygonList) {
            result.add(interceptPolygon(cacheMat, polygon, rect, backgroundColor));
        }

        cacheMat.release();
        return result;
    }

    /**
     * 高级合成
     */
    public static Mat synthesisImage(SynthesisEntity synthesisEntity) {

        final Font font = new Font("Microsoft YaHei", Font.BOLD, 14);
        FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
        final Scalar backgroundColor = new Scalar(255, 255, 255, 255);
        int desWidth = synthesisEntity.getDesWidth();
        int desHeight = synthesisEntity.getDesHeight();
        List<Polygon> srcAreaPolygons = synthesisEntity.getSrcAreaPolygons();
        List<Polygon> desAreaPolygons = synthesisEntity.getDesAreaPolygons();
        List<Integer> angles = synthesisEntity.getAngles();

        // 1.生成完整图片
        Mat result = new Mat(desHeight, desWidth, CvType.CV_8UC3, backgroundColor);
        for (int i = 0; i < desAreaPolygons.size(); i++) {
            Polygon desPolygon = desAreaPolygons.get(i);
            Polygon srcPolygon = srcAreaPolygons.get(i);
            Integer angle = angles.get(i);

            // 1.1绘制掩码
            Mat mask = createMask(desWidth, desHeight, desPolygon.getXpoints(), desPolygon.getYpoints());
            Mat src = synthesisEntity.getSrc().clone();

            int width = src.width();
            int height = src.height();
            int xOffset = desWidth - width;
            int yOffset = desHeight - height;
            if (xOffset > 0 || yOffset > 0) {
                Core.copyMakeBorder(src, src, 0, Math.max(yOffset, 0), 0, Math.max(xOffset, 0), 0, backgroundColor);
            }

            // 1.2旋转原图
            if (angle != 0) {
                double x = srcPolygon.getCenterX();
                double y = srcPolygon.getCenterY();
                double panelCenterX = (double) desWidth / 2.0;
                double panelCenterY = (double) desHeight / 2.0;
                Vector translateVector = PointUtil.getTranslateVector(x, y, panelCenterX, panelCenterY);
                srcPolygon.translate(translateVector);
                ImageUtil.translateTransform(src, translateVector.getX(), translateVector.getY());

                src = ImageUtil.rotateTransform(src, angle, panelCenterX, panelCenterY);
                srcPolygon.rotateTransform(new com.symaster.common.geom.entity.Point(panelCenterX, panelCenterY), angle);
            }

            // 1.4移动原图
            Vector translateVector = PointUtil.getTranslateVector(srcPolygon, desPolygon);
            srcPolygon.translate(translateVector);
            ImageUtil.translateTransform(src, translateVector.getX(), translateVector.getY());

            // 1.3原图尺寸与type统一
            width = src.width();
            height = src.height();
            if (width != desWidth || height != desHeight) {
                src = new Mat(src, new Rect(0, 0, desWidth, desHeight));
            }

            // 1.5根据掩码绘制像素
            src.copyTo(result, mask);
        }

        // 2.绘制编码
        BufferedImage image = (BufferedImage) HighGui.toBufferedImage(result);
        Graphics2D g = image.createGraphics();

        SynthesisEntity.CodingFrameInfo codingFrameInfo = synthesisEntity.getCodingFrameInfo();
        List<String> printTexts = codingFrameInfo.getPrintTexts();
        List<Color> colors = codingFrameInfo.getColors();
        List<Double> codingFrameInfoAngles = codingFrameInfo.getAngles();
        List<Point> points = codingFrameInfo.getPoints();

        for (int i = 0; i < printTexts.size(); i++) {
            String printText = printTexts.get(i);
            Color color = colors.get(i);
            Double angle = codingFrameInfoAngles.get(i);
            Point point = points.get(i);

            // 绘制底色
            double x = point.x;
            double y = point.y;
            int w = metrics.stringWidth(printText) + 4;
            int h = metrics.getHeight() + 2;
            double[] xPoints = {x, x + w, x + w, x};
            double[] yPoints = {y + 4, y + 4, y + 4 - h, y + 4 - h};
            Polygon polygon = new Polygon(xPoints, yPoints);
            if (angle != 0) {
                polygon.rotateTransform(new com.symaster.common.geom.entity.Point(x, y), angle);
            }
            double[] xPoints1 = polygon.getXpoints();
            double[] yPoints1 = polygon.getYpoints();
            int[] xPointsInt = new int[xPoints1.length];
            int[] yPointsInt = new int[yPoints1.length];
            for (int m = 0; m < xPoints1.length; m++) {
                xPointsInt[m] = (int) xPoints1[m];
                yPointsInt[m] = (int) yPoints1[m];
            }
            g.setColor(color);
            g.fillPolygon(xPointsInt, yPointsInt, 4);

            // 绘制文本
            g.setColor(Color.BLACK);
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g.setFont(font);
            if (angle != 0) {
                g.rotate(Math.toRadians(angle), x, y);
            }
            if (angle < 0) {
                g.drawString(printText, (int) x + 2, (int) y - 1);
            } else {
                g.drawString(printText, (int) x + 2, (int) y - 1);
            }
            if (angle != 0) {
                g.rotate(Math.toRadians(-1 * angle), x, y);
            }
        }

        return bufferedImageRGB2MatBGR(image);
    }

    public static void synthesisImage(Mat floor, Polygon dst, Polygon src, Double angle, Mat img) {
        synthesisImage(floor, dst, src, angle, img, new Scalar(255, 255, 255, 255));
    }

    public static void synthesisImage(Mat floor, Polygon dst, Polygon src, Double angle, Mat img, Scalar backgroundColor) {
        if (angle == null) {
            angle = 0.0;
        }

        int dstWidth = floor.width();
        int dstHeight = floor.height();

        // 1.1绘制掩码
        Mat mask = createMask(dstWidth, dstHeight, dst.getXpoints(), dst.getYpoints());

        int width = img.width();
        int height = img.height();
        int xOffset = dstWidth - width;
        int yOffset = dstHeight - height;
        if (xOffset > 0 || yOffset > 0) {
            Core.copyMakeBorder(img, img, 0, Math.max(yOffset, 0), 0, Math.max(xOffset, 0), 0, backgroundColor);
        }

        // 1.2旋转原图
        if (angle != 0) {
            double x = src.getCenterX();
            double y = src.getCenterY();
            double panelCenterX = (double) dstWidth / 2.0;
            double panelCenterY = (double) dstHeight / 2.0;
            Vector translateVector = PointUtil.getTranslateVector(x, y, panelCenterX, panelCenterY);
            src.translate(translateVector);
            ImageUtil.translateTransform(img, translateVector.getX(), translateVector.getY());

            img = ImageUtil.rotateTransform(img, angle, panelCenterX, panelCenterY);
            src.rotateTransform(new com.symaster.common.geom.entity.Point(panelCenterX, panelCenterY), angle);
        }

        // 1.4移动原图
        Vector translateVector = PointUtil.getTranslateVector(src, dst);
        src.translate(translateVector);
        ImageUtil.translateTransform(img, translateVector.getX(), translateVector.getY());

        // 1.3原图尺寸与type统一
        width = img.width();
        height = img.height();
        if (width != dstWidth || height != dstHeight) {
            img = new Mat(img, new Rect(0, 0, dstWidth, dstHeight));
        }

        // 1.5根据掩码绘制像素
        img.copyTo(floor, mask);
    }

    /**
     * 将图片的宽高缩放或裁剪至指定宽高
     */
    public static Mat shearImage(Mat mat, int imageWidth, int imageHeight) {
        if (mat.width() > imageWidth) {
            Rect rect = new Rect(0, 0, imageWidth, imageHeight);
            return new Mat(mat, rect);
        } else {
            Core.copyMakeBorder(mat, mat, 0, (imageHeight - mat.height()), 0, (imageWidth - mat.width()), 0, new Scalar(0, 0, 0, 0));
            return mat;
        }
    }

    private static void splitByPolygonTest() {
        Mat imread = Imgcodecs.imread("D:\\106to162\\ADS-3WCSTY05-00000-FFFFFF-L-0000Q60T-120DPI.jpg");

        Polygon polygon = new Polygon(new double[]{0, 500, 500, 0}, new double[]{0, 0, 500, 500});
        polygon.move(200, 200);
        ArrayList<Polygon> polygons = new ArrayList<>();
        polygons.add(polygon);

        List<Mat> mats = splitByPolygon(imread, polygons, false, new Scalar(0, 0, 0));

        for (Mat mat : mats) {
            Imgcodecs.imwrite("D:\\106to162\\AUSDYVOAUSDYVOA.tif", mat);
        }
    }

    public static byte[] setDPI(byte[] bytes, int dpi, String forName) {
        String type = formatImageType(forName);
        if (StringUtils.isEmpty(type)) {
            throw ExceptionFactory.getDefaultException("无法识别文件类型");
        }

        byte[] result = null;
        if (TYPE_JPEG.equals(forName)) {
            result = setDPI(bytes, dpi);
        } else {
            throw ExceptionFactory.getDefaultException("不支持的文件类型");
        }

        return result;
    }

    /**
     * 目前只支持jpg修改
     */
    public static byte[] setDPI(BufferedImage image, int dpi, String formatName) {

        byte[] result = null;
        try {
            for (Iterator<ImageWriter> iw = ImageIO.getImageWritersByFormatName(formatName); iw.hasNext(); ) {

                ImageWriter writer = iw.next();
                ImageWriteParam writeParam = writer.getDefaultWriteParam();
                writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                writeParam.setCompressionQuality(1f);

                ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
                IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);

                if (metadata.isReadOnly() || !metadata.isStandardMetadataFormatSupported()) {
                    continue;
                }

                setDPI(metadata, dpi);

                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                MemoryCacheImageOutputStream memoryCacheImageOutputStream = new MemoryCacheImageOutputStream(bos);
                try {
                    writer.setOutput(memoryCacheImageOutputStream);
                    writer.write(metadata, new IIOImage(image, null, metadata), writeParam);
                    result = bos.toByteArray();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    memoryCacheImageOutputStream.close();
                    bos.close();
                }
                break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }

    /**
     * 目前只支持jpg修改
     */
    public static byte[] setDPI(byte[] bytes, int dpi) {
        byte[] result = new byte[0];
        try {
            result = setDPI(ImageIO.read(new ByteArrayInputStream(bytes)), dpi, "jpg");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    public static void close(Mat... mats) {
        if (mats != null && mats.length > 0) {
            for (Mat mat : mats) {
                if (mat != null) {
                    mat.release();
                }
            }
        }
    }

    private static void setDPI(IIOMetadata metadata, int dpi) throws IIOInvalidTreeException {
        String metadataFormat = "javax_imageio_jpeg_image_1.0";
        IIOMetadataNode root = new IIOMetadataNode(metadataFormat);
        IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
        IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");

        IIOMetadataNode app0JFIF = new IIOMetadataNode("app0JFIF");
        app0JFIF.setAttribute("majorVersion", "1");
        app0JFIF.setAttribute("minorVersion", "2");
        app0JFIF.setAttribute("thumbWidth", "0");
        app0JFIF.setAttribute("thumbHeight", "0");
        app0JFIF.setAttribute("resUnits", DENSITY_UNITS_PIXELS_PER_INCH);
        app0JFIF.setAttribute("Xdensity", String.valueOf(dpi));
        app0JFIF.setAttribute("Ydensity", String.valueOf(dpi));

        root.appendChild(jpegVariety);
        root.appendChild(markerSequence);
        jpegVariety.appendChild(app0JFIF);

        metadata.mergeTree(metadataFormat, root);
    }

    public static String formatImageType(String forName) {
        int i = forName.indexOf(".");
        forName = (i != -1) ? forName.substring(i + 1).toLowerCase() : forName.toLowerCase();

        for (String typeName : imageTypeMap.keySet()) {
            for (String type : imageTypeMap.get(typeName).split("/")) {
                if (type.equals(forName)) {
                    return typeName;
                }
            }
        }

        return null;
    }

    public static class SynthesisEntity {
        private Mat src;
        private int desWidth;
        private int desHeight;
        private List<Polygon> srcAreaPolygons;
        private List<Polygon> desAreaPolygons;
        private List<Integer> angles;
        private Boolean drawBorder;
        private Color borderColor;
        private CodingFrameInfo codingFrameInfo;

        public CodingFrameInfo getCodingFrameInfo() {
            return codingFrameInfo;
        }

        public void setCodingFrameInfo(CodingFrameInfo codingFrameInfo) {
            this.codingFrameInfo = codingFrameInfo;
        }

        public Boolean getDrawBorder() {
            return drawBorder;
        }

        public void setDrawBorder(Boolean drawBorder) {
            this.drawBorder = drawBorder;
        }

        public Color getBorderColor() {
            return borderColor;
        }

        public void setBorderColor(Color borderColor) {
            this.borderColor = borderColor;
        }

        public Mat getSrc() {
            return src;
        }

        public void setSrc(Mat src) {
            this.src = src;
        }

        public int getDesWidth() {
            return desWidth;
        }

        public void setDesWidth(int desWidth) {
            this.desWidth = desWidth;
        }

        public int getDesHeight() {
            return desHeight;
        }

        public void setDesHeight(int desHeight) {
            this.desHeight = desHeight;
        }

        public List<Polygon> getSrcAreaPolygons() {
            return srcAreaPolygons;
        }

        public void setSrcAreaPolygons(List<Polygon> srcAreaPolygons) {
            this.srcAreaPolygons = srcAreaPolygons;
        }

        public List<Polygon> getDesAreaPolygons() {
            return desAreaPolygons;
        }

        public void setDesAreaPolygons(List<Polygon> desAreaPolygons) {
            this.desAreaPolygons = desAreaPolygons;
        }

        public List<Integer> getAngles() {
            return angles;
        }

        public void setAngles(List<Integer> angles) {
            this.angles = angles;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            SynthesisEntity that = (SynthesisEntity) o;
            return desWidth == that.desWidth && desHeight == that.desHeight && Objects.equals(src, that.src) && Objects.equals(srcAreaPolygons, that.srcAreaPolygons) && Objects.equals(desAreaPolygons, that.desAreaPolygons) && Objects.equals(angles, that.angles) && Objects.equals(drawBorder, that.drawBorder) && Objects.equals(borderColor, that.borderColor) && Objects.equals(codingFrameInfo, that.codingFrameInfo);
        }

        @Override
        public int hashCode() {
            return Objects.hash(src, desWidth, desHeight, srcAreaPolygons, desAreaPolygons, angles, drawBorder, borderColor, codingFrameInfo);
        }

        @Override
        public String toString() {
            return "SynthesisEntity{" +
                    "src=" + src +
                    ", desWidth=" + desWidth +
                    ", desHeight=" + desHeight +
                    ", srcAreaPolygons=" + srcAreaPolygons +
                    ", desAreaPolygons=" + desAreaPolygons +
                    ", angles=" + angles +
                    ", drawBorder=" + drawBorder +
                    ", borderColor=" + borderColor +
                    ", codingFrameInfo=" + codingFrameInfo +
                    '}';
        }

        public static class CodingFrameInfo {

            // 文本
            private List<String> printTexts;
            // 颜色
            private List<Color> colors;
            // 所在坐标
            private List<Point> points;
            // 旋转角度
            private List<Double> angles;

            public List<String> getPrintTexts() {
                return printTexts;
            }

            public void setPrintTexts(List<String> printTexts) {
                this.printTexts = printTexts;
            }

            public List<Color> getColors() {
                return colors;
            }

            public void setColors(List<Color> colors) {
                this.colors = colors;
            }

            public List<Point> getPoints() {
                return points;
            }

            public void setPoints(List<Point> points) {
                this.points = points;
            }

            public List<Double> getAngles() {
                return angles;
            }

            public void setAngles(List<Double> angles) {
                this.angles = angles;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) {
                    return true;
                }
                if (o == null || getClass() != o.getClass()) {
                    return false;
                }
                CodingFrameInfo that = (CodingFrameInfo) o;
                return Objects.equals(printTexts, that.printTexts) && Objects.equals(colors, that.colors) && Objects.equals(points, that.points) && Objects.equals(angles, that.angles);
            }

            @Override
            public int hashCode() {
                return Objects.hash(printTexts, colors, points, angles);
            }

            @Override
            public String toString() {
                return "CodingFrameInfo{" +
                        "printTexts=" + printTexts +
                        ", colors=" + colors +
                        ", points=" + points +
                        ", angles=" + angles +
                        '}';
            }
        }
    }
}
